package com.jt.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面,Spring容器默认不能识别切面注解,需要手动配置(SpringConfig类，@EnableAspectJAutoProxy)
@Aspect
public class SpringAOP {

    private Object result;

    //定义切入点表达式
    @Pointcut("@annotation(com.jt.anno.Cache)")
    public void pointcut(){
        System.out.println("添加部门信息");
    }

    /**
     * within(com.jt.*.DeptServiceImpl) 一级包下的DeptServiceImpl类
     * within(com.jt..*.DeptServiceImpl) 多级包下的DeptServiceImpl类
     * within(com.jt..*) 包下的所有的类
     *
     * execution(返回值类型 包名.类名.方法名(参数列表))
     * execution(* com.jt..*.DeptServiceImpl.add*())
     * 注释：返回值类型任意的，com.jt下的所有包中的DeptServiceImpl类的add开头的方法，并且没有参数
     * @Before("execution(* com.jt..*.*(..))")
     * 注释：返回值类型任意，com.jt下的所有包中的所有类的所有方法，任意参数
     *
     * @Before("execution(int com.jt..*.*(..))")
     * @Before("execution(Intger com.jt..*.*(..))")
     * 强调：在Spring表达式中没有自动拆装箱功能！注意参数类型
     *
     * 注解形式：@annotation(包名.注解名)
     */
    //@Before("bean(deptServiceImpl)")
    //@Before("within(com.jt..*)")
    //@Before("execution(* com.jt..*.DeptServiceImpl.add*())")
    //@Before("@annotation(com.jt.anno.Cache)")
    /**
     * 定义before通知
     * 说明：前置通知，在目标方法执行之前执行
     * 用途：如果需要记录程序在方法执行前的状态,则使用前置通知
     * @param joinPoint
     * Spring为了AOP动态获取目标对象及方法中的数据，则通过JoinPoint对象，进行数据传递
     */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("我是before通知");
        System.out.println("获取目标对象的类型：" + joinPoint.getTarget().getClass());
        System.out.println("获取目标对象的类名：" + joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象的方法名：" + joinPoint.getSignature().getName());
        System.out.println("获取方法参数：" + Arrays.toString(joinPoint.getArgs()));
    }

    @After("pointcut()")
    public void after(){
        System.out.println("我是after通知");
    }

    /**
     *记录方法的方法返回
     *pointcut:关联的切入点表达式
     *returning: 将方法的返回值,通过形参result进行传递
     *@AfterReturning(pointcut = "pointcut()",returning = "result")
     *注意事项:
     *  如果参数中需要添加joinPoint 对象时,参数必须位于第一位.
     *  Spring在进行参数赋值时,采用index[0] 下标的方式赋值
     *报错提示: ::0xxxx
     *
     * @param joinPoint
     * @param result
     */
    //@AfterReturning("@annotation(com.jt.anno.Cache)")
    @AfterReturning(pointcut = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println("我是afterReturning通知");
        System.out.println("用户的返回值结果："+result);
    }

    /**
     * @param e
     * throwing = "e" 动态接收程序运行时的报错信息, 利用异常通知进行记录
     */
    @AfterThrowing(pointcut = "pointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("我是afterThrowing通知");
        System.out.println("获取异常信息："+e.getMessage());
        System.out.println("获取异常信息："+e.getClass());
    }

    /**
     *关于环绕通知的说明
     * 作用：可以控制目标方法是否执行
     * @param proceedingJoinPoint 通过proceed()控制目标方法执行
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        Object result=null;
        System.out.println("环绕通知开始");
        //1.执行下一个通知 2.执行目标方法
        try {
            long start = System.currentTimeMillis();
            Thread.sleep(3000);
            result = proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println("耗时" + (end - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return null;
    }
}
